home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 5 / Skunkware 5.iso / src / Tools / gdiff-2.2 / diff.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-05-03  |  23.7 KB  |  929 lines

  1. /* GNU DIFF main routine.
  2.    Copyright (C) 1988, 1989, 1992 Free Software Foundation, Inc.
  3.  
  4. This file is part of GNU DIFF.
  5.  
  6. GNU DIFF is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2, or (at your option)
  9. any later version.
  10.  
  11. GNU DIFF is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15.  
  16. You should have received a copy of the GNU General Public License
  17. along with GNU DIFF; see the file COPYING.  If not, write to
  18. the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
  19.  
  20. /* GNU DIFF was written by Mike Haertel, David Hayes,
  21.    Richard Stallman, Len Tower, and Paul Eggert.  */
  22.  
  23. #define GDIFF_MAIN
  24. #include "diff.h"
  25. #include "getopt.h"
  26. #include "fnmatch.h"
  27.  
  28. #ifndef DEFAULT_WIDTH
  29. #define DEFAULT_WIDTH 130
  30. #endif
  31.  
  32. #ifndef GUTTER_WIDTH_MINIMUM
  33. #define GUTTER_WIDTH_MINIMUM 3
  34. #endif
  35.  
  36. int diff_dirs ();
  37. int diff_2_files ();
  38.  
  39. static int compare_files ();
  40. static int specify_format ();
  41. static void add_regexp();
  42. static void specify_style ();
  43. static void usage ();
  44.  
  45. /* Nonzero for -r: if comparing two directories,
  46.    compare their common subdirectories recursively.  */
  47.  
  48. static int recursive;
  49.  
  50. /* For debugging: don't do discard_confusing_lines.  */
  51.  
  52. int no_discards;
  53.  
  54. /* Return a string containing the command options with which diff was invoked.
  55.    Spaces appear between what were separate ARGV-elements.
  56.    There is a space at the beginning but none at the end.
  57.    If there were no options, the result is an empty string.
  58.  
  59.    Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
  60.    the length of that vector.  */
  61.  
  62. static char *
  63. option_list (optionvec, count)
  64.      char **optionvec;  /* Was `vector', but that collides on Alliant.  */
  65.      int count;
  66. {
  67.   int i;
  68.   int length = 0;
  69.   char *result;
  70.  
  71.   for (i = 0; i < count; i++)
  72.     length += strlen (optionvec[i]) + 1;
  73.  
  74.   result = (char *) xmalloc (length + 1);
  75.   result[0] = 0;
  76.  
  77.   for (i = 0; i < count; i++)
  78.     {
  79.       strcat (result, " ");
  80.       strcat (result, optionvec[i]);
  81.     }
  82.  
  83.   return result;
  84. }
  85.  
  86. /* Convert STR to a positive integer, storing the result in *OUT. 
  87.    If STR is not a valid integer, return -1 (otherwise 0). */
  88. static int
  89. ck_atoi (str, out)
  90.      char *str;
  91.      int *out;
  92. {
  93.   char *p;
  94.   for (p = str; *p; p++)
  95.     if (*p < '0' || *p > '9')
  96.       return -1;
  97.  
  98.   *out = atoi (optarg);
  99.   return 0;
  100. }
  101.  
  102. /* Keep track of excluded file name patterns.  */
  103.  
  104. static const char **exclude;
  105. static int exclude_alloc, exclude_count;
  106.  
  107. int
  108. excluded_filename (f)
  109.      const char *f;
  110. {
  111.   int i;
  112.   for (i = 0;  i < exclude_count;  i++)
  113.     if (fnmatch (exclude[i], f, 0) == 0)
  114.       return 1;
  115.   return 0;
  116. }
  117.  
  118. static void
  119. add_exclude (pattern)
  120.      const char *pattern;
  121. {
  122.   if (exclude_alloc <= exclude_count)
  123.     exclude = (const char **)
  124.           (exclude_alloc == 0
  125.            ? xmalloc ((exclude_alloc = 64) * sizeof (*exclude))
  126.            : xrealloc (exclude, (exclude_alloc *= 2) * sizeof (*exclude)));
  127.  
  128.   exclude[exclude_count++] = pattern;
  129. }
  130.  
  131. static int
  132. add_exclude_file (name)
  133.      const char *name;
  134. {
  135.   struct file_data f;
  136.   char *p, *q, *lim;
  137.  
  138.   f.name = optarg;
  139.   f.desc = strcmp (optarg, "-") == 0 ? 0 : open (optarg, O_RDONLY, 0);
  140.   if (f.desc < 0 || fstat (f.desc, &f.stat) != 0)
  141.     return -1;
  142.  
  143.   sip (&f, 1);
  144.   slurp (&f);
  145.  
  146.   for (p = f.buffer, lim = p + f.buffered_chars;  p < lim;  p = q)
  147.     {
  148.       q = memchr (p, '\n', lim - p);
  149.       if (!q)
  150.     q = lim;
  151.       *q++ = 0;
  152.       add_exclude (p);
  153.     }
  154.  
  155.   return close (f.desc);
  156. }
  157.  
  158. /* The numbers 129- that appear in the fourth element of some entries
  159.    tell the big switch in `main' how to process those options.  */
  160.  
  161. static struct option longopts[] =
  162. {
  163.   {"ignore-blank-lines", 0, 0, 'B'},
  164.   {"context", 2, 0, 'C'},
  165.   {"ifdef", 1, 0, 'D'},
  166.   {"show-function-line", 1, 0, 'F'},
  167.   {"speed-large-files", 0, 0, 'H'},
  168.   {"ignore-matching-lines", 1, 0, 'I'},
  169.   {"label", 1, 0, 'L'},
  170.   {"file-label", 1, 0, 'L'},    /* An alias, no longer recommended */
  171.   {"new-file", 0, 0, 'N'},
  172.   {"entire-new-file", 0, 0, 'N'},    /* An alias, no longer recommended */
  173.   {"unidirectional-new-file", 0, 0, 'P'},
  174.   {"starting-file", 1, 0, 'S'},
  175.   {"initial-tab", 0, 0, 'T'},
  176.   {"width", 1, 0, 'W'},
  177.   {"text", 0, 0, 'a'},
  178.   {"ascii", 0, 0, 'a'},        /* An alias, no longer recommended */
  179.   {"ignore-space-change", 0, 0, 'b'},
  180.   {"minimal", 0, 0, 'd'},
  181.   {"ed", 0, 0, 'e'},
  182.   {"forward-ed", 0, 0, 'f'},
  183.   {"ignore-case", 0, 0, 'i'},
  184.   {"paginate", 0, 0, 'l'},
  185.   {"print", 0, 0, 'l'},        /* An alias, no longer recommended */
  186.   {"rcs", 0, 0, 'n'},
  187.   {"show-c-function", 0, 0, 'p'},
  188.   {"binary", 0, 0, 'q'},    /* An alias, no longer recommended */
  189.   {"brief", 0, 0, 'q'},
  190.   {"recursive", 0, 0, 'r'},
  191.   {"report-identical-files", 0, 0, 's'},
  192.   {"expand-tabs", 0, 0, 't'},
  193.   {"version", 0, 0, 'v'},
  194.   {"ignore-all-space", 0, 0, 'w'},
  195.   {"exclude", 1, 0, 'x'},
  196.   {"exclude-from", 1, 0, 'X'},
  197.   {"side-by-side", 0, 0, 'y'},
  198.   {"unified", 2, 0, 'U'},
  199.   {"left-column", 0, 0, 129},
  200.   {"suppress-common-lines", 0, 0, 130},
  201.   {"sdiff-merge-assist", 0, 0, 131},
  202.   {"old-line-format", 1, 0, 132},
  203.   {"new-line-format", 1, 0, 133},
  204.   {"unchanged-line-format", 1, 0, 134},
  205.   {"old-group-format", 1, 0, 135},
  206.   {"new-group-format", 1, 0, 136},
  207.   {"unchanged-group-format", 1, 0, 137},
  208.   {"changed-group-format", 1, 0, 138},
  209.   {0, 0, 0, 0}
  210. };
  211.  
  212. int
  213. main (argc, argv)
  214.      int argc;
  215.      char *argv[];
  216. {
  217.   int val;
  218.   int c;
  219.   int prev = -1;
  220.   extern char *version_string;
  221.   int width = DEFAULT_WIDTH;
  222.  
  223.   program = argv[0];
  224.  
  225.   /* Do our initializations. */
  226.   output_style = OUTPUT_NORMAL;
  227.   always_text_flag = FALSE;
  228.   ignore_space_change_flag = FALSE;
  229.   ignore_all_space_flag = FALSE;
  230.   length_varies = FALSE;
  231.   ignore_case_flag = FALSE;
  232.   ignore_blank_lines_flag = FALSE;
  233.   ignore_regexp_list = NULL;
  234.   function_regexp_list = NULL;
  235.   print_file_same_flag = FALSE;
  236.   entire_new_file_flag = FALSE;
  237.   unidirectional_new_file_flag = FALSE;
  238.   no_details_flag = FALSE;
  239.   context = -1;
  240.   line_end_char = '\n';
  241.   tab_align_flag = FALSE;
  242.   tab_expand_flag = FALSE;
  243.   recursive = FALSE;
  244.   paginate_flag = FALSE;
  245.   heuristic = FALSE;
  246.   dir_start_file = NULL;
  247.   msg_chain = NULL;
  248.   msg_chain_end = NULL;
  249.   no_discards = 0;
  250.  
  251.   /* Decode the options.  */
  252.  
  253.   while ((c = getopt_long (argc, argv,
  254.                "0123456789abBcC:dD:efF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:y",
  255.                longopts, (int *)0)) != EOF)
  256.     {
  257.       switch (c)
  258.     {
  259.       /* All digits combine in decimal to specify the context-size.  */
  260.     case '1':
  261.     case '2':
  262.     case '3':
  263.     case '4':
  264.     case '5':
  265.     case '6':
  266.     case '7':
  267.     case '8':
  268.     case '9':
  269.     case '0':
  270.       if (context == -1)
  271.         context = 0;
  272.       /* If a context length has already been specified,
  273.          more digits allowed only if they follow right after the others.
  274.          Reject two separate runs of digits, or digits after -C.  */
  275.       else if (prev < '0' || prev > '9')
  276.         fatal ("context length specified twice");
  277.  
  278.       context = context * 10 + c - '0';
  279.       break;
  280.  
  281.     case 'a':
  282.       /* Treat all files as text files; never treat as binary.  */
  283.       always_text_flag = 1;
  284.       break;
  285.  
  286.     case 'b':
  287.       /* Ignore changes in amount of whitespace.  */
  288.       ignore_space_change_flag = 1;
  289.       length_varies = 1;
  290.       break;
  291.  
  292.     case 'B':
  293.       /* Ignore changes affecting only blank lines.  */
  294.       ignore_blank_lines_flag = 1;
  295.       break;
  296.  
  297.     case 'C':        /* +context[=lines] */
  298.     case 'U':        /* +unified[=lines] */
  299.       if (optarg)
  300.         {
  301.           if (context >= 0)
  302.         fatal ("context length specified twice");
  303.  
  304.           if (ck_atoi (optarg, &context))
  305.         fatal ("invalid context length argument");
  306.         }
  307.  
  308.       /* Falls through.  */
  309.     case 'c':
  310.       /* Make context-style output.  */
  311.       specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
  312.       break;
  313.  
  314.     case 'd':
  315.       /* Don't discard lines.  This makes things slower (sometimes much
  316.          slower) but will find a guaranteed minimal set of changes.  */
  317.       no_discards = 1;
  318.       break;
  319.  
  320.     case 'D':
  321.       /* Make merged #ifdef output.  */
  322.       specify_style (OUTPUT_IFDEF);
  323.       {
  324.         int i, err = 0;
  325.         static const char C_ifdef_group_formats[] =
  326.           "#ifndef %s\n%%<#endif /* not %s */\n%c#ifdef %s\n%%>#endif /* %s */\n%c%%=%c#ifndef %s\n%%<#else /* %s */\n%%>#endif /* %s */\n";
  327.         char *b = xmalloc (sizeof (C_ifdef_group_formats)
  328.                    + 7 * strlen(optarg) - 14 /* 7*"%s" */
  329.                    - 8 /* 5*"%%" + 3*"%c" */);
  330.         sprintf (b, C_ifdef_group_formats,
  331.              optarg, optarg, 0,
  332.              optarg, optarg, 0, 0,
  333.              optarg, optarg, optarg);
  334.         for (i = 0; i < 4; i++)
  335.           {
  336.         err |= specify_format (&group_format[i], b);
  337.         b += strlen (b) + 1;
  338.           }
  339.         if (err)
  340.           error ("conflicting #ifdef formats", 0, 0);
  341.       }
  342.       break;
  343.  
  344.     case 'e':
  345.       /* Make output that is a valid `ed' script.  */
  346.       specify_style (OUTPUT_ED);
  347.       break;
  348.  
  349.     case 'f':
  350.       /* Make output that looks vaguely like an `ed' script
  351.          but has changes in the order they appear in the file.  */
  352.       specify_style (OUTPUT_FORWARD_ED);
  353.       break;
  354.  
  355.     case 'F':
  356.       /* Show, for each set of changes, the previous line that
  357.          matches the specified regexp.  Currently affects only
  358.          context-style output.  */
  359.       add_regexp (&function_regexp_list, optarg);
  360.       break;
  361.  
  362.     case 'h':
  363.       /* Split the files into chunks of around 1500 lines
  364.          for faster processing.  Usually does not change the result.
  365.  
  366.          This currently has no effect.  */
  367.       break;
  368.  
  369.     case 'H':
  370.       /* Turn on heuristics that speed processing of large files
  371.          with a small density of changes.  */
  372.       heuristic = 1;
  373.       break;
  374.  
  375.     case 'i':
  376.       /* Ignore changes in case.  */
  377.       ignore_case_flag = 1;
  378.       break;
  379.  
  380.     case 'I':
  381.       /* Ignore changes affecting only lines that match the
  382.          specified regexp.  */
  383.       add_regexp (&ignore_regexp_list, optarg);
  384.       break;
  385.  
  386.     case 'l':
  387.       /* Pass the output through `pr' to paginate it.  */
  388.       paginate_flag = 1;
  389.       break;
  390.  
  391.     case 'L':
  392.       /* Specify file labels for `-c' output headers.  */
  393.       if (!file_label[0])
  394.         file_label[0] = optarg;
  395.       else if (!file_label[1])
  396.         file_label[1] = optarg;
  397.       else
  398.         fatal ("too many file label options");
  399.       break;
  400.       
  401.     case 'n':
  402.       /* Output RCS-style diffs, like `-f' except that each command
  403.          specifies the number of lines affected.  */
  404.       specify_style (OUTPUT_RCS);
  405.       break;
  406.  
  407.     case 'N':
  408.       /* When comparing directories, if a file appears only in one
  409.          directory, treat it as present but empty in the other.  */
  410.       entire_new_file_flag = 1;
  411.       break;
  412.  
  413.     case 'p':
  414.       /* Make context-style output and show name of last C function.  */
  415.       specify_style (OUTPUT_CONTEXT);
  416.       add_regexp (&function_regexp_list, "^[_a-zA-Z$]");
  417.       break;
  418.  
  419.     case 'P':
  420.       /* When comparing directories, if a file appears only in
  421.          the second directory of the two,
  422.          treat it as present but empty in the other.  */
  423.       unidirectional_new_file_flag = 1;
  424.       break;
  425.  
  426.     case 'q':
  427.       no_details_flag = 1;
  428.       break;
  429.  
  430.     case 'r':
  431.       /* When comparing directories, 
  432.          recursively compare any subdirectories found.  */
  433.       recursive = 1;
  434.       break;
  435.  
  436.     case 's':
  437.       /* Print a message if the files are the same.  */
  438.       print_file_same_flag = 1;
  439.       break;
  440.  
  441.     case 'S':
  442.       /* When comparing directories, start with the specified
  443.          file name.  This is used for resuming an aborted comparison.  */
  444.       dir_start_file = optarg;
  445.       break;
  446.  
  447.     case 't':
  448.       /* Expand tabs to spaces in the output so that it preserves
  449.          the alignment of the input files.  */
  450.       tab_expand_flag = 1;
  451.       break;
  452.  
  453.     case 'T':
  454.       /* Use a tab in the output, rather than a space, before the
  455.          text of an input line, so as to keep the proper alignment
  456.          in the input line without changing the characters in it.  */
  457.       tab_align_flag = 1;
  458.       break;
  459.  
  460.     case 'u':
  461.       /* Output the context diff in unidiff format.  */
  462.       specify_style (OUTPUT_UNIFIED);
  463.       break;
  464.  
  465.     case 'v':
  466.       fprintf (stderr, "GNU diff version %s\n", version_string);
  467.       break;
  468.  
  469.     case 'w':
  470.       /* Ignore horizontal whitespace when comparing lines.  */
  471.       ignore_all_space_flag = 1;
  472.       length_varies = 1;
  473.       break;
  474.  
  475.     case 'x':
  476.       add_exclude (optarg);
  477.       break;
  478.  
  479.     case 'X':
  480.       if (add_exclude_file (optarg) != 0)
  481.         pfatal_with_name (optarg);
  482.       break;
  483.  
  484.     case 'y':
  485.       /* Use side-by-side (sdiff-style) columnar output. */
  486.       specify_style (OUTPUT_SDIFF);
  487.       break;
  488.  
  489.     case 'W':
  490.       /* Set the line width for OUTPUT_SDIFF.  */
  491.       if (ck_atoi (optarg, &width) || width <= 0)
  492.         fatal ("column width must be a positive integer");
  493.       break;
  494.       
  495.     case 129:
  496.       sdiff_left_only = 1;
  497.       break;
  498.       
  499.     case 130:
  500.       sdiff_skip_common_lines = 1;
  501.       break;
  502.       
  503.     case 131:
  504.       /* sdiff-style columns output. */
  505.       specify_style (OUTPUT_SDIFF);
  506.       sdiff_help_sdiff = 1;
  507.       break;
  508.  
  509.     case 132:
  510.     case 133:
  511.     case 134:
  512.       specify_style (OUTPUT_IFDEF);
  513.       {
  514.         const char **form = &line_format[c - 132];
  515.         if (*form && strcmp (*form, optarg) != 0)
  516.           error ("conflicting line format", 0, 0);
  517.         *form = optarg;
  518.       }
  519.       break;
  520.  
  521.     case 135:
  522.     case 136:
  523.     case 137:
  524.     case 138:
  525.       specify_style (OUTPUT_IFDEF);
  526.       {
  527.         const char **form = &group_format[c - 135];
  528.         if (*form && strcmp (*form, optarg) != 0)
  529.           error ("conflicting group format", 0, 0);
  530.         *form = optarg;
  531.       }
  532.       break;
  533.  
  534.     default:
  535.       usage ();
  536.     }
  537.       prev = c;
  538.     }
  539.  
  540.   if (optind != argc - 2)
  541.     usage ();
  542.  
  543.  
  544.   {
  545.     /*
  546.      *    We maximize first the half line width, and then the gutter width,
  547.      *    according to the following constraints:
  548.      *    1.  Two half lines plus a gutter must fit in a line.
  549.      *    2.  If the half line width is nonzero:
  550.      *        a.  The gutter width is at least GUTTER_WIDTH_MINIMUM.
  551.      *        b.  If tabs are not expanded to spaces,
  552.      *        a half line plus a gutter is an integral number of tabs,
  553.      *        so that tabs in the right column line up.
  554.      */
  555.     int t = tab_expand_flag ? 1 : TAB_WIDTH;
  556.     int off = (width + t + GUTTER_WIDTH_MINIMUM) / (2*t)  *  t;
  557.     sdiff_half_width = max (0, min (off - GUTTER_WIDTH_MINIMUM, width - off)),
  558.     sdiff_column2_offset = sdiff_half_width ? off : width;
  559.   }
  560.  
  561.   if (output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED)
  562.     context = 0;
  563.   else if (context == -1)
  564.     /* Default amount of context for -c.  */
  565.     context = 3;
  566.  
  567.   if (output_style == OUTPUT_IFDEF)
  568.     {
  569.       int i;
  570.       char **p;
  571.       for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++)
  572.     if (!line_format[i]) {
  573.       p = &line_format[i];
  574.       *p = "%l\n";
  575.     }
  576.       if (!group_format[OLD]) {
  577.         p = &group_format[OLD];
  578.     *p = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%<";
  579.       }
  580.       if (!group_format[NEW]) {
  581.     p = &group_format[NEW];
  582.     *p = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%>";
  583.       }
  584.       if (!group_format[UNCHANGED]) {
  585.     p = &group_format[UNCHANGED];
  586.     *p = "%=";
  587.       }
  588.       if (!group_format[CHANGED]) {
  589.     p = &group_format[CHANGED];
  590.     *p = concat (group_format[OLD],    group_format[NEW], "");
  591.       }
  592.     }
  593.  
  594.   no_diff_means_no_output =
  595.     (output_style == OUTPUT_IFDEF ?
  596.       (!*group_format[UNCHANGED]
  597.        || (strcmp (group_format[UNCHANGED], "%=") == 0
  598.        && !*line_format[UNCHANGED]))
  599.      : output_style == OUTPUT_SDIFF ? sdiff_skip_common_lines : 1);
  600.  
  601.   switch_string = option_list (argv + 1, optind - 1);
  602.  
  603.   val = compare_files (0, argv[optind], 0, argv[optind + 1], 0);
  604.  
  605.   /* Print any messages that were saved up for last.  */
  606.   print_message_queue ();
  607.  
  608.   if (ferror (stdout) || fclose (stdout) != 0)
  609.     fatal ("write error");
  610.   exit (val);
  611.   return val;
  612. }
  613.  
  614. /* Add the compiled form of regexp PATTERN to REGLIST.  */
  615.  
  616. static void
  617. add_regexp (reglist, pattern)
  618.      struct regexp_list **reglist;
  619.      char *pattern;
  620. {
  621.   struct regexp_list *r;
  622.   const char *m;
  623.  
  624.   r = (struct regexp_list *) xmalloc (sizeof (*r));
  625.   bzero (r, sizeof (*r));
  626.   r->buf.fastmap = (char *) xmalloc (256);
  627.   m = re_compile_pattern (pattern, strlen (pattern), &r->buf);
  628.   if (m != 0)
  629.     error ("%s: %s", pattern, m);
  630.  
  631.   /* Add to the start of the list, since it's easier than the end.  */
  632.   r->next = *reglist;
  633.   *reglist = r;
  634. }
  635.  
  636. static void
  637. usage ()
  638. {
  639.   fprintf (stderr, "Usage: %s [options] from-file to-file\n", program);
  640.   fprintf (stderr, "Options:\n\
  641.        [-abBcdefhHilnNpPqrstTuvwy] [-C lines] [-D name] [-F regexp]\n\
  642.        [-I regexp] [-L from-label [-L to-label]] [-S starting-file] [-U lines]\n\
  643.        [-W columns] [-x pattern] [-X pattern-file] [--exclude=pattern]\n\
  644.        [--exclude-from=pattern-file] [--ignore-blank-lines] [--context[=lines]]\n\
  645.        [--ifdef=name] [--show-function-line=regexp] [--speed-large-files]\n\
  646.        [--label=from-label [--label=to-label]] [--new-file]\n");
  647.   fprintf (stderr, "\
  648.        [--ignore-matching-lines=regexp] [--unidirectional-new-file]\n\
  649.        [--starting-file=starting-file] [--initial-tab] [--width=columns]\n\
  650.        [--text] [--ignore-space-change] [--minimal] [--ed] [--forward-ed]\n\
  651.        [--ignore-case] [--paginate] [--rcs] [--show-c-function] [--brief]\n\
  652.        [--recursive] [--report-identical-files] [--expand-tabs] [--version]\n");
  653.   fprintf (stderr, "\
  654.        [--ignore-all-space] [--side-by-side] [--unified[=lines]]\n\
  655.        [--left-column] [--suppress-common-lines] [--sdiff-merge-assist]\n\
  656.        [--old-line-format=format] [--new-line-format=format]\n\
  657.        [--unchanged-line-format=format]\n\
  658.        [--old-group-format=format] [--new-group-format=format]\n\
  659.        [--unchanged-group-format=format] [--changed-group-format=format]\n");
  660.   exit (2);
  661.  
  662. static int
  663. specify_format (var, value)
  664.      const char **var;
  665.      const char *value;
  666. {
  667.   int err = *var ? strcmp (*var, value) : 0;
  668.   *var = value;
  669.   return err;
  670. }
  671.  
  672. static void
  673. specify_style (style)
  674.      enum output_style style;
  675. {
  676.   if (output_style != OUTPUT_NORMAL
  677.       && output_style != style)
  678.     error ("conflicting specifications of output style", 0, 0);
  679.   output_style = style;
  680. }
  681.  
  682. /* Compare two files (or dirs) with specified names
  683.    DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion.
  684.    (if DIR0 is 0, then the name is just NAME0, etc.)
  685.    This is self-contained; it opens the files and closes them.
  686.  
  687.    Value is 0 if files are the same, 1 if different,
  688.    2 if there is a problem opening them.  */
  689.  
  690. static int
  691. compare_files (dir0, name0, dir1, name1, depth)
  692.      char *dir0, *dir1;
  693.      char *name0, *name1;
  694.      int depth;
  695. {
  696.   struct file_data inf[2];
  697.   register int i;
  698.   int val;
  699.   int same_files;
  700.   int errorcount = 0;
  701.  
  702.   /* If this is directory comparison, perhaps we have a file
  703.      that exists only in one of the directories.
  704.      If so, just print a message to that effect.  */
  705.  
  706.   if (! ((name0 != 0 && name1 != 0)
  707.      || (unidirectional_new_file_flag && name1 != 0)
  708.      || entire_new_file_flag))
  709.     {
  710.       char *name = name0 == 0 ? name1 : name0;
  711.       char *dir = name0 == 0 ? dir1 : dir0;
  712.       message ("Only in %s: %s\n", dir, name);
  713.       /* Return 1 so that diff_dirs will return 1 ("some files differ").  */
  714.       return 1;
  715.     }
  716.  
  717.   /* Mark any nonexistent file with -1 in the desc field.  */
  718.   /* Mark unopened files (i.e. directories) with -2. */
  719.  
  720.   inf[0].desc = name0 == 0 ? -1 : -2;
  721.   inf[1].desc = name1 == 0 ? -1 : -2;
  722.  
  723.   /* Now record the full name of each file, including nonexistent ones.  */
  724.  
  725.   if (name0 == 0)
  726.     name0 = name1;
  727.   if (name1 == 0)
  728.     name1 = name0;
  729.  
  730.   inf[0].name = dir0 == 0 ? name0 : concat (dir0, "/", name0);
  731.   inf[1].name = dir1 == 0 ? name1 : concat (dir1, "/", name1);
  732.  
  733.   /* Stat the files.  Record whether they are directories.  */
  734.  
  735.   for (i = 0; i <= 1; i++)
  736.     {
  737.       bzero (&inf[i].stat, sizeof (struct stat));
  738.       inf[i].dir_p = 0;
  739.  
  740.       if (inf[i].desc != -1)
  741.     {
  742.       int stat_result;
  743.  
  744.       if (strcmp (inf[i].name, "-") == 0)
  745.         {
  746.           inf[i].desc = 0;
  747.           inf[i].name = "Standard Input";
  748.           stat_result = fstat (0, &inf[i].stat);
  749.         }
  750.       else
  751.         stat_result = stat (inf[i].name, &inf[i].stat);
  752.  
  753.       if (stat_result != 0)
  754.         {
  755.           perror_with_name (inf[i].name);
  756.           errorcount = 1;
  757.         }
  758.       else
  759.         inf[i].dir_p = S_ISDIR (inf[i].stat.st_mode) && inf[i].desc != 0;
  760.     }
  761.     }
  762.  
  763.   if (name0 == 0)
  764.     inf[0].dir_p = inf[1].dir_p;
  765.   if (name1 == 0)
  766.     inf[1].dir_p = inf[0].dir_p;
  767.  
  768.   if (errorcount == 0 && depth == 0 && inf[0].dir_p != inf[1].dir_p)
  769.     {
  770.       /* If one is a directory, and it was specified in the command line,
  771.      use the file in that dir with the other file's basename.  */
  772.  
  773.       int fnm_arg = inf[0].dir_p;
  774.       int dir_arg = 1 - fnm_arg;
  775.       char *p = rindex (inf[fnm_arg].name, '/');
  776.       char *filename = inf[dir_arg].name
  777.     = concat (inf[dir_arg].name,  "/", (p ? p+1 : inf[fnm_arg].name));
  778.  
  779.       if (inf[fnm_arg].desc == 0)
  780.     fatal ("can't compare - to a directory");
  781.  
  782.       if (stat (filename, &inf[dir_arg].stat) != 0)
  783.     {
  784.       perror_with_name (filename);
  785.       errorcount = 1;
  786.     }
  787.       else
  788.     inf[dir_arg].dir_p = S_ISDIR (inf[dir_arg].stat.st_mode);
  789.     }
  790.  
  791.   if (errorcount)
  792.     {
  793.  
  794.       /* If either file should exist but does not, return 2.  */
  795.  
  796.       val = 2;
  797.  
  798.     }
  799.   else if ((same_files =    inf[0].stat.st_ino == inf[1].stat.st_ino
  800.              && inf[0].stat.st_dev == inf[1].stat.st_dev
  801.              && inf[0].desc != -1
  802.              && inf[1].desc != -1)
  803.        && no_diff_means_no_output)
  804.     {
  805.       /* The two named files are actually the same physical file.
  806.      We know they are identical without actually reading them.  */
  807.  
  808.       val = 0;
  809.     }
  810.   else if (inf[0].dir_p & inf[1].dir_p)
  811.     {
  812.       if (output_style == OUTPUT_IFDEF)
  813.     fatal ("-D option not supported with directories");
  814.  
  815.       /* If both are directories, compare the files in them.  */
  816.  
  817.       if (depth > 0 && !recursive)
  818.     {
  819.       /* But don't compare dir contents one level down
  820.          unless -r was specified.  */
  821.       message ("Common subdirectories: %s and %s\n",
  822.            inf[0].name, inf[1].name);
  823.       val = 0;
  824.     }
  825.       else
  826.     {
  827.       val = diff_dirs (inf, compare_files, depth);
  828.     }
  829.  
  830.     }
  831.   else if (inf[0].dir_p | inf[1].dir_p)
  832.     {
  833.       /* Perhaps we have a subdirectory that exists only in one directory.
  834.      If so, just print a message to that effect.  */
  835.  
  836.       if (inf[0].desc == -1 || inf[1].desc == -1)
  837.     {
  838.       if (recursive
  839.           && (entire_new_file_flag
  840.           || (unidirectional_new_file_flag && inf[0].desc == -1)))
  841.         val = diff_dirs (inf, compare_files, depth);
  842.       else
  843.         {
  844.           char *dir = (inf[0].desc == -1) ? dir1 : dir0;
  845.           message ("Only in %s: %s\n", dir, name0);
  846.           val = 1;
  847.         }
  848.     }
  849.       else
  850.     {
  851.       /* We have a subdirectory in one directory
  852.          and a file in the other.  */
  853.  
  854.       message ("%s is a directory but %s is not\n",
  855.            inf[1 - inf[0].dir_p].name, inf[inf[0].dir_p].name);
  856.  
  857.       /* This is a difference.  */
  858.       val = 1;
  859.     }
  860.     }
  861.   else if (no_details_flag
  862.        && inf[0].stat.st_size != inf[1].stat.st_size
  863.        && (inf[0].desc == -1 || S_ISREG (inf[0].stat.st_mode))
  864.        && (inf[1].desc == -1 || S_ISREG (inf[1].stat.st_mode)))
  865.     {
  866.       message ("Files %s and %s differ\n", inf[0].name, inf[1].name);
  867.       val = 1;
  868.     }
  869.   else
  870.     {
  871.       /* Both exist and neither is a directory.  */
  872.  
  873.       /* Open the files and record their descriptors.  */
  874.  
  875.       if (inf[0].desc == -2)
  876.     if ((inf[0].desc = open (inf[0].name, O_RDONLY, 0)) < 0)
  877.       {
  878.         perror_with_name (inf[0].name);
  879.         errorcount = 1;
  880.       }
  881.       if (inf[1].desc == -2)
  882.     if (same_files)
  883.       inf[1].desc = inf[0].desc;
  884.     else if ((inf[1].desc = open (inf[1].name, O_RDONLY, 0)) < 0)
  885.       {
  886.         perror_with_name (inf[1].name);
  887.         errorcount = 1;
  888.       }
  889.     
  890.       /* Compare the files, if no error was found.  */
  891.  
  892.       val = errorcount ? 2 : diff_2_files (inf, depth);
  893.  
  894.       /* Close the file descriptors.  */
  895.  
  896.       if (inf[0].desc >= 0 && close (inf[0].desc) != 0)
  897.     {
  898.       perror_with_name (inf[0].name);
  899.       val = 2;
  900.     }
  901.       if (inf[1].desc >= 0 && inf[0].desc != inf[1].desc
  902.       && close (inf[1].desc) != 0)
  903.     {
  904.       perror_with_name (inf[1].name);
  905.       val = 2;
  906.     }
  907.     }
  908.  
  909.   /* Now the comparison has been done, if no error prevented it,
  910.      and VAL is the value this function will return.  */
  911.  
  912.   if (val == 0 && !inf[0].dir_p)
  913.     {
  914.       if (print_file_same_flag)
  915.     message ("Files %s and %s are identical\n",
  916.          inf[0].name, inf[1].name);
  917.     }
  918.   else
  919.     fflush (stdout);
  920.  
  921.   if (dir0 != 0)
  922.     free (inf[0].name);
  923.   if (dir1 != 0)
  924.     free (inf[1].name);
  925.  
  926.   return val;
  927. }
  928.